package com.xquant.trade.risk.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xquant.common.core.utils.StringUtils;
import com.xquant.common.engine.xPP.cxf.NotifyDataFacade;
import com.xquant.common.engine.xPP.request.CalcEngineParam;
import com.xquant.common.engine.xPP.request.CalcPricingParam;
import com.xquant.common.engine.xPP.request.Instrument;
import com.xquant.common.engine.xPP.response.CalcMarket;
import com.xquant.common.engine.xPP.response.Contract;
import com.xquant.common.security.annotation.RequiresPermissions;
import com.xquant.common.security.utils.ExcelUtil;
import com.xquant.common.security.utils.SecurityUtils;
import com.xquant.trade.utils.ResponseVo;
import com.xquant.trade.risk.entity.CombinedMonitor;
import com.xquant.trade.risk.entity.CombinedMonitorDetail;
import com.xquant.trade.risk.service.RiskService;
import com.xquant.trade.trade.entity.Trade;
import com.xquant.trade.trade.entity.TradeDurationDTO;
import com.xquant.trade.trade.service.impl.TradeServiceImpl;
import com.xquant.trade.utils.CacheUtil;
import com.xquant.trade.utils.CommResponse;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @author ershuai.zhao
 */
@RestController
@RequestMapping("monitor")
public class MonitorRiskController {
    private static final Logger logger = LoggerFactory.getLogger(MonitorRiskController.class);

    @Autowired
    private RiskService riskService;
    @Autowired
    private TradeServiceImpl tradeService;
    @Autowired
    private NotifyDataFacade notifyDataFacade;
    //IO线程工厂类
    static  ThreadFactory threadFactory = new  ThreadFactory() {
        @Override
        public Thread newThread(@NonNull Runnable runnable) {
            Thread thread = new Thread(runnable);
            thread.setName("calcTpAll-pool-");
            return thread;
        }
    };
    private static ThreadPoolExecutor executors = new ThreadPoolExecutor(10,
            20,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE),threadFactory,new ThreadPoolExecutor.DiscardPolicy());
    /**
     * 账户监控
     */
    @RequestMapping(value = "/calcAccountMonitor.action")
    @RequiresPermissions("risk:fundmanage:fundmonitor:calc")
    public CommResponse calcAccountMonitor(@RequestParam Map<String, Object> param, HttpServletRequest request) throws ExecutionException, InterruptedException{
        //1、通知行情变化
        notifyDataFacade.notifyDailySet();
        CacheUtil.getInstance().removeAllCacheData();
        if (StrUtil.isNotBlank(MapUtils.getString(param, "accIds"))) {
            if (JSONUtil.parseArray(MapUtils.getString(param, "accIds")).size() > 0) {
                param.put("accIds", JSONUtil.parseArray(MapUtils.getString(param, "accIds")).toArray(String.class));
            }else {
                param.put("accIds", null);
            }
        }

        if (StrUtil.isNotBlank(MapUtils.getString(param, "underlyerIds"))) {
            if (JSONUtil.parseArray(MapUtils.getString(param, "underlyerIds")).size() > 0) {
                param.put("underlyerIds", JSONUtil.parseArray(MapUtils.getString(param, "underlyerIds")).toArray(String.class));
            } else {
                param.put("underlyerIds", null);
            }
        }
        //3、查询交易列表  场内对冲交易  期权交易
        List<Trade> tradeList = new ArrayList<>();
        String[] tradeTypes;
        if (StrUtil.equals(MapUtil.getStr(param, "aTypes"), "0")) {
            tradeTypes = new String[]{"OtcOptionSell", "OtcOptionBuy"};
        } else if (StrUtil.equals(MapUtil.getStr(param, "aTypes"), "1")) {
            tradeTypes = new String[]{"LongOpen", "LongClose", "ShortOpen", "ShortClose"};
        } else {
            tradeTypes = new String[]{"OtcOptionSell", "OtcOptionBuy", "LongOpen", "LongClose", "ShortOpen", "ShortClose"};
        }
        param.put("tradeType", tradeTypes);
        if (StrUtil.isNotBlank(MapUtils.getString(param, "groupId"))) {
            param.put("groupId", JSONUtil.parseArray(MapUtils.getString(param, "groupId")).toArray(String.class));
        }
        param.put("userId", SecurityUtils.getUserId());
        //按界面传入的日期查询后再用实际交易日计算
        //2、查询计算日期 交易日
        if (StringUtils.isNotNull(MapUtil.getStr(param, "dealDate"))) {
            param.put("calendarDate", MapUtil.getStr(param, "dealDate"));
        } else {
            param.put("calendarDate", riskService.getBlcDate());

        }
        param.put("blcDate", param.get("calendarDate")); //筛选交易到期日筛选
        param.put("calcDate", param.get("calendarDate")); //筛选计算持仓数据的日期
        tradeList = riskService.queryMonitor4TradeList(param);
        // 盘中管理，不处理计算日  xpp
        String blcDate = MapUtil.getStr(param,"calendarDate");
        param.put("blcDate", blcDate);
        param.put("calcDate",blcDate);
        //4、开始处理期权交易 和 场内交易
        List<CombinedMonitorDetail> ret = new ArrayList<>();
        if (tradeList.size() > 0) {
            List<Callable<CombinedMonitorDetail>> tasks = new ArrayList<Callable<CombinedMonitorDetail>>();//添加任务
            for (int i = 0; i < tradeList.size(); i++) {
//                System.out.println("监控第" + (i + 1) + "个计算");
                Trade trade = tradeList.get(i);
                CombinedMonitorDetail monitorDetail = new CombinedMonitorDetail();
                // 设置uCode
                monitorDetail.setTradeId(trade.getTradeId());
                monitorDetail.setTradeType(trade.getTradeType());
                monitorDetail.setuICode(trade.getuICode());
                monitorDetail.setuAType(trade.getuAType());
                monitorDetail.setuMType(trade.getuMType());
                monitorDetail.setuIName(trade.getuIName());
                monitorDetail.setUuICode(trade.getuICode());
                monitorDetail.setUuAType(trade.getuAType());
                monitorDetail.setUuMType(trade.getuMType());
                monitorDetail.setUuIName(trade.getuIName());
                monitorDetail.setiName(trade.getuIName());
                monitorDetail.setCalcPrice(trade.getuPrice());
                monitorDetail.setVolume(trade.getVolume().doubleValue());
                monitorDetail.setProductType(trade.getProductType());
                monitorDetail.setPartyId(trade.getPartyId());
                monitorDetail.setPartyName(trade.getPartyName());

                //交易要素
                monitorDetail.setOpeningPrice(trade.getCalcPrice());
                monitorDetail.setPayDirection(trade.getPayDirection());
                monitorDetail.setRealizedYield(trade.getRealizedYield());
                monitorDetail.setYield(trade.getYield());
                monitorDetail.setHisYield(trade.getHisYield());
                monitorDetail.setExtTradeId(trade.getExtTradeId());
                monitorDetail.setTradeDate(trade.getTradeDate());
                monitorDetail.setOptionType(trade.getOptionType());
                monitorDetail.setStrikePrice(trade.getStrikePrice());
                monitorDetail.setLowStrikePrice(trade.getLowStrikePrice());
                monitorDetail.setYield1(trade.getYield1());
                monitorDetail.setYield2(trade.getYield2());
                monitorDetail.setYield3(trade.getYield3());
                monitorDetail.setTriggerPrice1(trade.getTriggerPrice1());
                monitorDetail.setTriggerPrice2(trade.getTriggerPrice2());
                monitorDetail.setFrontOptionFee(trade.getFrontOptionFee());
                monitorDetail.setFrontOptionFeePaydate(trade.getFrontOptionFeePaydate());
                monitorDetail.setFrontFee(trade.getFrontFee());
                monitorDetail.setFrontFeePaydate(trade.getFrontFeePaydate());
                monitorDetail.setPaymentDate(trade.getPaymentDate());
                monitorDetail.setTerminateFee(trade.getTerminateFee());
                monitorDetail.setParticipationRate1(trade.getParticipationRate1());
                monitorDetail.setParticipationRate2(trade.getParticipationRate2());
                monitorDetail.setFixedRate(trade.getFixedRate());
                monitorDetail.setDuEventStatus(trade.getDuEventStatus());
                monitorDetail.setCalcDate(param.get("calcDate").toString());
                /*期权费和前端费率 年化开关*/
                monitorDetail.setFrontFeeRate(trade.getFrontFeeRate());
                monitorDetail.setFrontFeeYear(trade.getFrontFeeYear());
                monitorDetail.setFrontOptionFeeRate(trade.getFrontOptionFeeRate());
                monitorDetail.setFrontOptionFeeYear(trade.getFrontOptionFeeYear());

                Callable<CombinedMonitorDetail> qfe = new Callable<CombinedMonitorDetail>() {
                    @Override
                    public CombinedMonitorDetail call() throws Exception {
                        //场内交易
                        if (!StrUtil.equals("CTM", trade.getaType())) {
                            return calcSimpleTrade(trade, monitorDetail);
                        } else { //期权交易
                            return calcOtcOptionTrade(param, blcDate, trade, monitorDetail);
                        }
                    }
                };
                tasks.add(qfe);
            }
            // 将计算结果添加到 list 可以让线程阻塞
//            int count=tasks.size();
//            int cycleCnt=count/50
            List<Future<CombinedMonitorDetail>> futures = executors.invokeAll(tasks);
            if (futures != null && futures.size() > 0) {
                for (Future<CombinedMonitorDetail> future : futures) {
                    ret.add(future.get());
                }
            }
        }
        // 将计算结果汇总
        List<CombinedMonitorDetail> errList = ret.stream().filter(l -> StrUtil.isNotBlank(l.getErrInfo()) && !"".equals(l.getErrInfo())).collect(Collectors.toList());
        Map<String, CombinedMonitor> rets = mergeCombinedMonitorDetail(ret);
        CommResponse res = new CommResponse(true, "success", rets.values(), "error", errList);
        CacheUtil.getInstance().removeAllCacheData();
        return res;
    }

    /**
     * 场内对冲交易 风险计算
     *
     * @param trade         交易明细
     * @param monitorDetail 计算结果明细
     */
    CombinedMonitorDetail calcSimpleTrade(Trade trade, CombinedMonitorDetail monitorDetail) {
        // 设置code
        monitorDetail.setiCode("*");
        monitorDetail.setaType(null);
        monitorDetail.setmType(null);
        //计算npv、delta、deltaCash
        monitorDetail.setAccName(trade.getAccName());
        monitorDetail.setVolume(Math.abs(trade.getAmount().doubleValue() * 1));
        monitorDetail.setNpv(Math.abs(trade.getAmount().doubleValue() * 1));
        monitorDetail.setDelta(Math.abs(trade.getAmount().doubleValue() / trade.getCalcPrice()));
        monitorDetail.setCashDelta(Math.abs(trade.getAmount().doubleValue()));
        return monitorDetail;
    }

    /**
     * 期权交易 风险计算
     *
     * @param param         请求参数
     * @param blcDate       业务日期
     * @param trade         交易明细
     * @param monitorDetail 计算结果明细
     */
    CombinedMonitorDetail calcOtcOptionTrade(Map param, String blcDate, Trade trade, CombinedMonitorDetail
            monitorDetail) {
        //期权交易
        monitorDetail.setiCode(trade.getiCode());
        monitorDetail.setaType(trade.getaType());
        monitorDetail.setmType(trade.getmType());

        monitorDetail.setIssueDate(trade.getIssueDate());
        monitorDetail.setMaturityDate(trade.getMaturityDate());
        monitorDetail.setMarginCost(trade.getMarginCost().doubleValue());
        monitorDetail.setDimAccId(trade.getAccId());
        monitorDetail.setAccId(trade.getAccId());
        monitorDetail.setAccName(trade.getAccName());
        monitorDetail.setAccIdCp(trade.getAccIdCp());
        monitorDetail.setAccNameCp(trade.getAccNameCp());
        monitorDetail.setpCalc(trade.getpCalc());
        monitorDetail.setExerciseType(trade.getExerciseType());
        monitorDetail.setPremium(trade.getPremium());

        //  获取前台传递 配置ID cfgId
        String cfgId = MapUtils.getString(param, "cfgId");
        //  获取条款
        String pCalc = MapUtils.getString(trade.getInstrumentInfo(), "pCalc");
        Instrument instrument = JSON.parseObject(pCalc, Instrument.class);
        /**************************计算准备*************************************/
        CalcPricingParam calcPricingParam = new CalcPricingParam();
        calcPricingParam.setValueDate(blcDate);
        calcPricingParam.setCfgId(cfgId);

        CalcEngineParam calcEngineParam = new CalcEngineParam();
        calcEngineParam.setInstrument(instrument);
        calcEngineParam.setCalcPricingParam(calcPricingParam);


        /*******************************计算估值**********************************/

        try {
            //ResponseVo res =ResponseVo res = calcEngineService.calcEngineByCfgId(ProductTypeEnum.OTC_OPTION.getCode(), RequestCodeEnum.CALC_MARKET.getCode(), calcEngineParam);
            ResponseVo res = ResponseVo.success();
            logger.info("估值结果：" + JSONUtil.toJsonStr(res));
            /*******************************计算后处理**********************************/
            if (res.getErrorCode() < 0) {
                monitorDetail.setErrInfo(res.getErrorMsg());

            } else {
                boolean dwh = false;
                Contract contract = ((CalcMarket) res.getResult()).getContract();
                monitorDetail.setGroupInfo(trade.getuICode());
                monitorDetail.setVolume(trade.getVolume().doubleValue());
                if (dwh) {
                    monitorDetail.setNpv(NumberUtil.mul(Double.parseDouble(contract.getNpv()), trade.getVolume()).doubleValue());
                    monitorDetail.setDelta(NumberUtil.mul(Double.parseDouble(contract.getDelta()), trade.getVolume()).doubleValue());
                    monitorDetail.setGamma(NumberUtil.mul(Double.parseDouble(contract.getGamma()), trade.getVolume()).doubleValue());
                    monitorDetail.setVega(NumberUtil.mul(Double.parseDouble(contract.getVega()), trade.getVolume()).doubleValue());
                    monitorDetail.setRho(NumberUtil.mul(Double.parseDouble(contract.getRho()), trade.getVolume()).doubleValue());
                    monitorDetail.setTheta(Double.parseDouble(contract.getTheta()) * trade.getVolume().doubleValue());
                    monitorDetail.setCashDelta(Double.parseDouble(contract.getDelta()) * Double.parseDouble(contract.getSpotPrice()) * trade.getVolume().doubleValue());
                    monitorDetail.setCashGamma(Double.parseDouble(contract.getGamma()) * Double.parseDouble(contract.getSpotPrice()) * Double.parseDouble(contract.getSpotPrice()) * trade.getVolume().doubleValue() / 100);
                    monitorDetail.setCalcPrice(Double.parseDouble(contract.getSpotPrice()));
                } else {
                    monitorDetail.setNpv(Double.parseDouble(contract.getNpv()));
                    monitorDetail.setDelta(Double.parseDouble(contract.getDelta()));
                    monitorDetail.setGamma(Double.parseDouble(contract.getGamma()));
                    monitorDetail.setVega(Double.parseDouble(contract.getVega()));
                    monitorDetail.setRho(Double.parseDouble(contract.getRho()));
                    monitorDetail.setTheta(Double.parseDouble(contract.getTheta()));
                    monitorDetail.setCashDelta(Double.parseDouble(contract.getDelta()) * Double.parseDouble(contract.getSpotPrice()));
                    monitorDetail.setCashGamma(Double.parseDouble(contract.getGamma()) * Double.parseDouble(contract.getSpotPrice()) * Double.parseDouble(contract.getSpotPrice()) / 100);
                    monitorDetail.setCalcPrice(Double.parseDouble(contract.getSpotPrice()));
                }


                monitorDetail.setPlFloat(monitorDetail.getNpv() - trade.getAmountCost().doubleValue());
                //如果前端费支付日和期权费支付日小于等于计算日，则浮动盈亏始终减去成本(包含前端费和期权费)
                JSONObject formData=JSON.parseObject(MapUtils.getString(trade.getInstrumentInfo(), "pForm"));
                String frontFeeDate=trade.getFrontFeePaydate();
                Double front =trade.getFrontFee() == null ? 0.0D : trade.getFrontFee().doubleValue();
                BigDecimal frontFee=new BigDecimal(front);
                String frontOptionFeeDate=trade.getFrontOptionFeePaydate();
                Double frontOption =trade.getFrontOptionFee() == null ? 0.0D : trade.getFrontOptionFee().doubleValue();
                BigDecimal frontOptionFee=new BigDecimal(frontOption);
                //费用有正负 以界面录入为准 后台不作判断
                if(frontFee.compareTo(BigDecimal.ZERO)!=0&&StrUtil.isNotBlank(frontFeeDate)&& DateUtil.compare(DateUtil.parseDate(frontFeeDate),DateUtil.parseDate(blcDate))<=0){
                    monitorDetail.setPlFloat((new BigDecimal(monitorDetail.getPlFloat()).add(frontFee)).doubleValue());
                }
                if(frontOptionFee.compareTo(BigDecimal.ZERO)!=0&&StrUtil.isNotBlank(frontOptionFeeDate)&& DateUtil.compare(DateUtil.parseDate(frontOptionFeeDate),DateUtil.parseDate(blcDate))<=0){
                    monitorDetail.setPlFloat((new BigDecimal(monitorDetail.getPlFloat()).add(frontOptionFee)).doubleValue());
                }
            }
        } catch (Exception ex) {
            monitorDetail.setErrInfo(ex.getMessage());

        }
        return monitorDetail;
    }

    /**
     * 组合监控
     *
     * @param
     * @param
     * @return
     */
    @RequiresPermissions("risk:profitmanage:profitmonitor:calc")
    @RequestMapping(value = "/calcCombinedMonitor.action")
    public CommResponse calcCombinedMonitor(@RequestParam Map<String, Object> param, HttpServletRequest request) throws ExecutionException, InterruptedException{
        //调用账户监控
        return this.calcAccountMonitor(param, request);
    }

    /**
     * 合并监控计算结果  获取标的行情
     */
    private Map<String, CombinedMonitor> mergeCombinedMonitorDetail(List<CombinedMonitorDetail> lst) {
        Map<String, CombinedMonitor> ret = new HashMap<>();
        for (int i = 0; i < lst.size(); i++) {
            CombinedMonitorDetail item = lst.get(i);
            if (StrUtil.isNotBlank(item.getErrInfo())) {
                continue;
            }
            String key = item.getUuCode() + "," + item.getGroupId();
            if (!ret.containsKey(key)) {
                ret.put(key, new CombinedMonitor());
                ret.get(key).setGroupInfo(item.getGroupInfo());
                ret.get(key).setSceneID(item.getSceneID());
                ret.get(key).setUuICode(item.getUuICode());
                ret.get(key).setUuAType(item.getUuAType());
                ret.get(key).setUuMType(item.getUuMType());
                ret.get(key).setUuIName(item.getUuIName());
                ret.get(key).setPriceOffset(item.getPriceOffset());
                ret.get(key).setVolOffset(item.getVolOffset());
                ret.get(key).setSpotPriceInc(item.getPriceOffset());
                ret.get(key).setVolSurfaceInc(item.getVolOffset());
                ret.get(key).setCalcPrice(item.getCalcPrice());
                ret.get(key).setGroupId(item.getGroupId());
                ret.get(key).setGroupName(item.getGroupName());

            }
            ret.get(key).getDetails().add(item);
        }
        return ret;
    }

    /**
     * newCloud  获取标的行情
     */
    @RequestMapping(value = "/getICodePrice.action")
    public CommResponse getICodePrice(@RequestParam Map<String, Object> param) {
        List<Map> list = riskService.getICodePrice(param);
        return new CommResponse(true, list);
    }

    /**
     * 根据组合查询包含的标的
     */
    @RequestMapping(value = "/getUnderLyByProfit.action")
    public CommResponse getUnderLyByProfit(String[] groupIds) {
        List<Map> list = riskService.getUnderLyByProfit(groupIds);
        return new CommResponse(true, list);
    }

    /**
     * 根据账户查询包含的标的
     */
    @RequestMapping(value = "/getUnderLyByFund.action")
    public CommResponse getUnderLyByFund(@RequestParam Map map) {
        String ids = (String) map.get("accIds");
        String[] accIds = ids.split(",");
        List<Map> list = riskService.getUnderLyByAcc(accIds);
        return new CommResponse(true, list);
    }

    /**
     * 导出监控明细
     */
    @PostMapping(value = "/exportFundMonitor.action")
    void exportFundMonitor(HttpServletResponse response, @RequestParam  Map<String, Object> param) {
        ExcelUtil<TradeDurationDTO> util = new ExcelUtil<>(TradeDurationDTO.class);
        List<TradeDurationDTO> detailList= riskService.queryMonitor4TradeDurationDTOList(param);
        util.exportExcel(response, detailList, "监控明细");
    }

}
